缘由
最近被问起这样的问题,所以把自己知道的写出来
包括 Web 端调用 Native 和 Native 调用 Web,算是记录一下
本文使用 Xcode 7.3 + Swift,最低兼容 iOS 8.0
这里查看源代码)是项目的完整代码,下载完成后,请执行 pod install
目录文件介绍
ViewController.swift
ViewController.swift 主要配合 Main.storyboard 绘制了 UI 元素
接下来绝大部分代码工作将在这个文件中进行viewDidLoad方法中主要是让 webView 加载 Bundle 中的 html 文件
JavaScriptCoreDemo.html
这个文件主要用来模拟实际使用时我们会遇到的 web 页面
Web 端调用 iOS 端代码
我将其分为了如下步骤
为 WebView 注册代理,并实现代理方法
webView.delegate = self
extension ViewController : UIWebViewDelegate {
func webViewDidFinishLoad(webView: UIWebView) {
// Waiting
}
}
获取到当前 Web 页面中的上下文
func webViewDidFinishLoad(webView: UIWebView) {
context = webView.valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") as? JSContext
// Waiting
}
注册回调
func webViewDidFinishLoad(webView: UIWebView) {
context = webView.valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") as? JSContext
let callBack : @convention(block) (AnyObject?) -> Void = { [weak self] (paramFromJS) -> Void in
// Waiting
}
context?.setObject(unsafeBitCast(callBack, AnyObject.self), forKeyedSubscript: "callNative")
}
展示 Web 页面传递过来的的参数
最后我们需要通知自己原生代码已经接受到了 web 端的调用,并且输出一下 web 页面传递过来了参数
在 ViewController 类中,实现 fromJS 方法:
func fromJS(paramFromJS:AnyObject?) {
Alert
.showIn(self)
.style(UIAlertControllerStyle.ActionSheet)
.title("Call From JS")
.message("检测到了来自 JS 的调用!")
.addAction("我知道了", style: .Cancel, handler: nil)
guard let paramFromJS = paramFromJS else {
return
}
print("param from JavaScript is:")
print(paramFromJS)
}
其中 Alert 类来自于 Halo.framework,用来通知自己原生代码已经接受到了 web 端的调用,对 UIAlertController 做了简单的封装,不必关心
在上一步中的 Waiting 注释处调用
let callBack : @convention(block) (AnyObject?) -> Void = { [weak self] (paramFromJS) -> Void in
self?.fromJS(paramFromJS)
}
这里的 self (也就是我们的 ViewController) 中被加上了 weak 修饰符,这是因为:
context?.setObject(unsafeBitCast(callBack, AnyObject.self), forKeyedSubscript: "callNative")
context 会强引用这个回调块,为了避免强引用,我们需要将 self 在 block 中声明为弱引用
至于 @convention(block) ,大家可以参看这里
PS: 还有一个问题,上面的代码大家是不是有点害怕?优雅的 Swift 怎么会出现 unsafeBitCast(callBack, AnyObject.self) 这样的代码!大家可以看看 这里
期待 iOS10 SDK 中 Swift 环境下,JSContext 可以实现下标访问
PS2: 另外,我在这里将 paramFromJS 声明为了 AnyObject? 类型,JavaScriptCore 是可以自动将 js 传递的参数做转换的,上面例子中,我们通过
print(paramFromJS!.dynamicType)
可以发现 paramFromJS 被转化为了__NSDictionaryM
关于 dynamicType,请看这里
运行程序
此时控制台输出:

模拟器:

iOS 端调用 Web 的 js 代码
可以分为两种方式
调用 Web 页面中已经实现好的 JS
我们的 web 页面中包含了一段已经写好的 JavaScript function:
function fromNative(args) {
document.getElementById("fromNativeParan").innerHTML = "来自原生代码的参数为" + args;
};
我们可以通过 JavaScriptContext 直接对其进行调用:
在 ViewController 类中,实现 callJS 方法:
func callJS() {
let params : [AnyObject]! = ["Hello JS! \(arc4random() % 10)"]
context?.objectForKeyedSubscript("fromNative").callWithArguments(params)
}
arc4random() % 10 的作用是为了产生随机取以区分多次点击
在 rightBarButtonClick 中调用:
@IBAction func rightBarButtonClick(sender: UIBarButtonItem) {
callJS()
}
web 页面产生了如下变化:

直接注入一段 JS 代码
在 ViewController 类中,实现 evaluateJS 方法:
func evaluateJS() {
context?.evaluateScript(“alert(\”你调用了 JS\”)”)
}
在 rightBarButtonClick 中调用:
@IBAction func rightBarButtonClick(sender: UIBarButtonItem) {
evaluateJS()
}
效果如图:

PS: 实际上 evaluateJS 还可以小城下面这种形式:
func evaluateJS() {
context?.evaluateScript("fromNative(' 你运行了一段JS')")
}
这时该方法的作用和 ViewController.callJS 就一样了
题外话
本来还想写点 WKWebView 的东西,但鉴于自己以前的使用经历,还是算了…
简单说一下使用 WKWebView 遇到的坑吧:
每个都是超级严重的 bug 有没有!
在产品经理面前直冒冷汗有没有!